import random
import re
from enum import Enum
from typing import Dict, List
from functools import wraps
from openpyxl import load_workbook, Workbook
from openpyxl.styles import Font, Alignment, PatternFill, Color
from api import config
from core import utils
import time

from logger.logger import Log


# 数据展示模块  excel具体内容读取模块

# GATEWAY SHEET
class GatewayCase:
    CmdMap = {}

    def __init__(self, gw_cmd_name, gw_cmd_group, gw_cmd):
        self.name = gw_cmd_name
        self.group = gw_cmd_group
        # 如果有第四列就取第四列，没有就取以$:开头的第三列数据
        self.command = re.findall(r'(?<=S:).+', gw_cmd)[0] if 'S:' in gw_cmd else gw_cmd


# NODE SHEET
class NodeCase:
    CmdMap = {}

    def __init__(self, node_cmd_name, group, node_cmd):
        self.name = node_cmd_name
        self.group = group
        self.command = re.findall(r'(?<=S:).+', node_cmd)[0]


class ResultInfo():

# 对输入数据进行预处理，特别是针对包含'[ERROR]'的内容进行提取，并根据数据长度进行一些基本的判断和处理
    def is_none(func):
        @wraps(func)
        def wrapper(self, data):
            # 匹配以 [ERROR] 开头，后面跟着任意字符（非贪婪匹配），直到下一个 [3 或字符串结束。re.S 标志表示 . 匹配包括换行符在内的任意字符。
            # 如果 data 的长度小于10，则返回一个自定义的 DataError 异常，提示“数据长度不够！”。
            # 否则，将 data 和 error_list 作为参数传递给原始函数 func 并返回其结果。
            error_list = re.findall('(?<=\[ERROR\]).+?(?=\[3|$)', data, re.S)
            if len(data) < 10:
                return self.DataError("数据长度不够！")
            else:
                return func(self, data, error_list)

        return wrapper

# 对于传入的数据进行了预处理，替换了其中的设备参数
# 通过正则表达式分别提取出了head和body部分的内容，然后进行判断
# 判断head是否存在于数据中，如果不存在则记录错误信息并将结果标记为False
# 如果body不包含'&'，则判断body是否存在于数据中；如果包含'&'，则分割成多个子字符串进行判断
# 最后使用正则表达式提取出时间信息，最后将判断的结果、消息以及提取到的时间信息和错误列表，并将结果返回
    @is_none
    def is_exist(self, data, error_list):
        Log.logger.info("THE BIG BEGIN")
        Log.logger.info(data)
        Log.logger.info("THE BIG END")
        self.err_list = error_list if len(error_list) > 0 else None
        self.msg = ''
        self.res = True
        for i in range(0, len(self.list)):
            temp_data = self.list[i]
            for i in config.Config.arg['Device']:
                if ('$' + i) in temp_data:
                    temp_data = temp_data.replace('$' + i, str(config.Config.arg['Device'][i]))
            head = re.findall(r'\[.+?(?=:)', temp_data)[0]
            body = re.findall(r'(?<=:).+', temp_data)[0]
            Log.logger.info("head:"+head)
            Log.logger.info("body:" + body)
            # Log.logger.info("data:" + data)
            if head not in data:
                self.msg += 'title {} Not Found'.format(head)
                self.res = False

            if '&' not in body:
                if body not in data:
                    self.msg += 'MSG: {} Not Found ;'.format(body)
                    self.res = False
            else:
                body_list = re.findall('[^&]+?(?=&|$)', body)
                for b in body_list:
                    if b not in data:
                        self.msg += 'MSG: {} Not Found ;'.format(b)
                        self.res = False
        time_list = re.findall('(?<=36;1m\[).+?(?=\])', data)
        if len(time_list) > 0:
            self.time = time_list[-1]
        return self.res, self.msg, self.time, self.err_list

    #@is_none
    def is_exist_multi(self):
        return self.list
        # self.err_list = None
        # self.msg = ''
        # return self.res, self.msg, self.time, self.err_list

    def DataError(self, error):
        return False, error, None, self.err_list

    def __init__(self, result):
        # 以 [ 开头、紧接着任意字符（非贪婪模式），直到遇到分号 ; 或字符串结尾 $ 为止的子串
        self.list = re.findall(r'\[.+?(?=;|$)', result)
        self.res = True
        self.msg = ''
        self.time = None
        self.err_list = None
        self.node_list = None
        self.start_time = None
        self.end_time = None


# ['TestCase']['case'] SHEET --> PERFORMANCE OR COMMON OR MULTI
class TestCase:
    # gw_cmd_list: object

    def __init__(self, name, remark, group, gwcase, ndcase, delay, timeout, cycle, clear, skip, result):
        self.name = name
        self.remark = remark
        self.group = group
        self.gw_cmd_list = gwcase
        self.nd_cmd_list = ndcase
        self.delay = int(delay) if type(delay) == str else delay
        self.timeout = int(timeout) if type(timeout) == str else timeout
        self.cycle = int(cycle) if type(cycle) == str else cycle
        self.fakeCycle = int(cycle) if type(cycle) == str else cycle
        self.NormalCode = 0
        self.NormalCode2 = 0
        self.result = result
        # 如果 self.result 中包含分号 ';'，则创建一个 ResultInfo 对象并将其赋值给 self.result_info
        # 如果 self.result 中不包含分号，则将 None 赋值给 self.result_info
        if self.result is not None:
            self.result_info = ResultInfo(self.result) if ';' in self.result else None
        else:
            self.result_info = None
        self.clear = True if clear == "是" else False
        self.skip = True if skip == "是" else False


class TestDevice:
    def __init__(self, name, remark, count, passed_count, gw_cmd, node_cmd, ave_use_time, start_time, end_time,
                 expect_res,
                 failed_res, error_list):
        self.name = name
        self.remark = remark
        self.count = count
        self.passed_count = passed_count
        self.gw_cmd = gw_cmd
        self.node_cmd = node_cmd
        self.ave_use_time = ave_use_time
        self.start_time = start_time
        self.end_time = end_time
        self.per = self.passed_count / self.count
        self.expect_res = expect_res
        self.failed_res = failed_res
        self.error_list = error_list
        self.low = 0
        self.up = 0
        self.ave = 0
        self.test_count = 0
        self.total_time = 0


class TestDeviceMul:
    def __init__(self, name, mac, count, cmd, time, per):
        self.name = name
        self.mac = mac
        self.count = count
        # 轮询总次数遇到多个maclist或nodelist时会叠加，所以添加这个参数
        self.countTimes = 0
        self.cmd = cmd
        self.time = time
        self.per = per
        self.low = 0
        self.up = 0
        self.ave = 0
        self.test_count = 0
        self.total_time = 0

    def cal_time(self, time):
        if self.low == 0:
            self.low = time
        if time > self.up:
            self.up = time

        # 测试通过次数
        self.test_count += 1
        # 测试总次数 新增
        self.countTimes += 1
        a = self.test_count
        self.total_time += time
        self.per = self.test_count / self.countTimes
        b = self.per
        self.ave = self.total_time / self.test_count

        if time < self.low:
            self.low = time


class TestResult:
    start: str
    end: str
    case = {}
    resultList = {}
    nodeList = {}

    def begin(self):
        self.start = utils.get_time()

    def finish(self):
        self.end = utils.get_time()

    def add_pass_case(self, usetime):
        self.list.append(round(usetime, 2))

    def add_failed_case(self, count):
        self.failed_list.append(count)

    def add_error_case(self, count):
        self.error_list.append(count)

    # def add_pass_case(self, usetime_dict):
    #     # 假设 usetime_dict 中包含需要舍入的值，比如 'time' 键对应的值
    #     time_value = usetime_dict.get('time', 0)  # 如果 'time' 键不存在，默认值为 0
    #     rounded_time = round(time_value, 2)
    #     self.list.append(rounded_time)

    def test_pass(self):
        self.pass_count += 1

    def test_failed(self):
        self.failed_count += 1

    def test_error(self):
        self.error_count += 1

    def __init__(self, name: str, cycle):
        self.name = name
        self.cycle = cycle
        self.pass_count = 0
        self.failed_count = 0
        self.error_count = 0
        self.list = []
        self.failed_list = []
        self.error_list = []
        # 测试总计次数
        self.times = 0


def get_time() -> str:
    return time.strftime("%Y%m%d_%H_%M_%S", time.localtime())


def read_excel_data(path: str, case_name: str) -> (
        Dict[str, List[GatewayCase]], Dict[str, List[NodeCase]], Dict[str, List[TestCase]]):
    """
    读取excel数据
    :param path: excel 路径
    :return:
    """
    excel = load_workbook(path)
    gateway = read_excel_gateway_case(excel)
    node = read_excel_node_case(excel)
    test_case = read_excel_test_case(excel, case_name)
    excel.close()
    return gateway, node, test_case


# 读excel表中GATEWAY数据
def read_excel_gateway_case(excel) -> Dict[str, List[GatewayCase]]:
    sheet = excel['gateway']
    rows = sheet.max_row
    index = 0
    cmd_column = 4 if config.Config.arg['TestCase']['type'] == 'mqtt' else 3
    case_list: List[GatewayCase] = []
    for i in range(3, rows + 1):
        # 第一、二、最后三列
        gw_cmd_name = sheet.cell(row=i, column=1).value
        gw_cmd_group = sheet.cell(row=i, column=2).value
        gw_cmd = sheet.cell(row=i, column=cmd_column).value
        # $DeviceId 根据config.toml中的列表替换
        for i in config.Config.arg['Device']:
            if ('$' + i) in gw_cmd:
                gw_cmd = gw_cmd.replace('$' + i, str(config.Config.arg['Device'][i])).replace("'", '"')
        case_list.append(GatewayCase(gw_cmd_name, gw_cmd_group, gw_cmd))
        gw_cmd_map = {gw_cmd_name: gw_cmd}
        GatewayCase.CmdMap.update(gw_cmd_map)
        index = index + 1
    case_map: Dict[str, List[GatewayCase]] = {}
    for case in case_list:
        if case.group is None:
            case_map[case.name] = [case]
        else:
            if case_map.get(case.group) is None:
                case_map[case.group] = []
            case_map[case.group].append(case)
    # for v in case_map.values():
    #     v.sort(key=lambda a: a.order)
    return case_map

# 读excel表中NODE数据
def read_excel_node_case(excel) -> Dict[str, List[NodeCase]]:
    sheet = excel['node']
    rows = sheet.max_row
    cols = sheet.max_column
    index = 0
    case_list: List[NodeCase] = []
    for i in range(3, rows + 1):
        node_cmd_name = sheet.cell(row=i, column=1).value
        group = sheet.cell(row=i, column=2).value
        node_cmd = sheet.cell(row=i, column=3).value
        case_list.append(NodeCase(node_cmd_name, group, node_cmd))
        node_cmd_map = {node_cmd_name: node_cmd}
        NodeCase.CmdMap.update(node_cmd_map)
        index = index + 1
    case_map: Dict[str, List[NodeCase]] = {}
    for case in case_list:
        if case.group is None:
            case_map[case.name] = [case]
        else:
            if case_map.get(case.group) is None:
                case_map[case.group] = []
            case_map[case.group].append(case)
    return case_map

# 读excel表中'TestCase']['case']数据
def read_excel_test_case(excel, case_name) -> Dict[str, List[TestCase]]:
    sheet = excel[case_name]
    rows = sheet.max_row
    cols = sheet.max_column
    index = 0
    case_list: List[TestCase] = []
    for i in range(3, rows + 1):
        name = sheet.cell(row=i, column=1).value
        remark = sheet.cell(row=i, column=2).value
        group = sheet.cell(row=i, column=3).value
        gw_cmd_list = re.findall('[^&]+?(?=&|$)', sheet.cell(row=i, column=4).value) if sheet.cell(row=i,
                                                                                                   column=4).value is not None else []
        node_cmd_list = re.findall('[^&]+?(?=&|$)', sheet.cell(row=i, column=5).value) if sheet.cell(row=i,
                                                                                                     column=5).value is not None else []
        delay = sheet.cell(row=i, column=6).value
        timeout = sheet.cell(row=i, column=7).value
        cycles = sheet.cell(row=i, column=8).value
        clear = sheet.cell(row=i, column=9).value
        skip = sheet.cell(row=i, column=10).value
        result = sheet.cell(row=i, column=11).value
        case_list.append(
            TestCase(name, remark, group, gw_cmd_list, node_cmd_list, delay, timeout, cycles, clear, skip, result))
        index = index + 1
    case_map: Dict[str, List[TestCase]] = {}
    for case in case_list:
        if case.group is None:
            case_map[case.name] = [case]
        else:
            if case_map.get(case.group) is None:
                case_map[case.group] = []
            case_map[case.group].append(case)
    return case_map


class DataType(Enum):
    STRING = "S"  # 普通字符串
    HEX = "H"  # 普通hex字符串 有空格


def ExportSingle(case, filename):
    book = Workbook()
    sheet = book.create_sheet('GW3_Test')
    sheet.merge_cells(start_row=1, start_column=1, end_row=1, end_column=9)
    sheet['A1'].font = Font(size=26, bold=True)
    sheet['A1'].alignment = Alignment(horizontal='center', vertical='center')
    title = ["用例编号", "用例名称", "网关命令", "模组命令", "测试次数", "通过次数", "成功率", "平均用时",
             "失败结果", "ERROR日志"]
    sheet['A1'].font = Font(size=26, bold=True)
    sheet['A1'].alignment = Alignment(horizontal='center', vertical='center')
    sheet['A1'].value = "网关3.0自动化测试统计结果 - {}".format(utils.get_time())
    i = 0
    for c in ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']:
        if c == 'A' or c == 'B' or c == 'C':
            sheet.column_dimensions[c].width = 35.0
        elif c == 'D' or c == 'E' or c == 'F' or c == 'H' or c == 'G':
            sheet.column_dimensions[c].width = 15.0
        else:
            sheet.column_dimensions[c].width = 30.0
        sheet[c + '2'].value = title[i]
        sheet[c + '2'].font = Font(size=18, bold=True, color=Color(indexed=1))
        sheet[c + '2'].fill = PatternFill(patternType='solid', fgColor=Color(indexed=32))
        i += 1
    sheet.row_dimensions[1].height = 40
    sheet.row_dimensions[2].height = 30
    row = 3
    count = 1
    for name, ts in case.items():
        try:
            sheet.row_dimensions[row].height = 30
            sheet['A' + str(row)].value = name
            sheet['A' + str(row)].font = Font(size=16, bold=True)
            sheet['B' + str(row)].value = ts.remark
            sheet['B' + str(row)].font = Font(size=16, bold=True)
            sheet['C' + str(row)].value = ts.gw_cmd
            sheet['C' + str(row)].font = Font(size=16, bold=True)
            sheet['D' + str(row)].value = ts.node_cmd
            sheet['D' + str(row)].font = Font(size=16, bold=True)
            sheet['E' + str(row)].value = ts.count
            sheet['E' + str(row)].font = Font(size=16, bold=True)
            sheet['F' + str(row)].value = ts.passed_count
            sheet['F' + str(row)].font = Font(size=16, bold=True)
            sheet['G' + str(row)].value = "%.2f%%" % (ts.per * 100)
            sheet['G' + str(row)].font = Font(size=16, bold=True)
            sheet['H' + str(row)].value = str(round(ts.ave_use_time, 2)) + '(s)'
            sheet['H' + str(row)].font = Font(size=16, bold=True)
            sheet['I' + str(row)].value = ts.failed_res
            sheet['I' + str(row)].font = Font(size=16, bold=True)
            sheet['J' + str(row)].value = ts.error_list
            sheet['J' + str(row)].font = Font(size=16, bold=True)
        except Exception as e:
            print(e)
        row += 1
        count += 1

    sheet_delete = book.get_sheet_by_name('Sheet')
    book.remove_sheet(sheet_delete)
    new_file = 'report/' + get_time() + '_' + filename
    book.save(new_file)
    # utils.open_file(new_file)

# 首先创建一个新的Excel工作簿对象book
# 遍历case字典，其中每个键值对表示一个sheet页，键为名称，值为包含数据的对象列表ts
# 对每个键值对，创建一个新的sheet页，设置标题样式，并填充表头信息
# 遍历对象列表ts，将对象的属性值填充到对应的单元格中
# 最后删除默认的'Sheet'页，保存Excel文件到指定路径下以当前时间和filename命名
def ExportMulticast(case, filename):
    book = Workbook()
    for name, ts in case.items():
        sheet = book.create_sheet(name)
        sheet.merge_cells(start_row=1, start_column=1, end_row=1, end_column=9)
        sheet['A1'].font = Font(size=26, bold=True)
        sheet['A1'].alignment = Alignment(horizontal='center', vertical='center')
        title = ["序号", "设备MAC地址", "轮询总次数", "轮询命令", "测试间隔", "成功率"]
        sheet['A1'].font = Font(size=26, bold=True)
        sheet['A1'].alignment = Alignment(horizontal='center', vertical='center')
        sheet['A1'].value = "{} - 多设备测试统计表 - {}".format(name, utils.get_time())
        # i 初始化为 0，用于迭代标题列表。
        # 通过迭代列表 ['A', 'B', 'C', 'D', 'E', 'F'] 中的元素 c，针对每个列设置不同的宽度。
        # 如果列为 'A'，则设置宽度为 10.0。
        # 如果列为 'C'、'E' 或 'F'，则设置宽度为 20.0。
        # 对于其他列，设置宽度为 30.0。
        # 在每列的第二行添加标题，并设置标题的字体、加粗、颜色和填充样式。
        # 设置第一行和第二行的行高分别为 40 和 30。
        # 初始化变量 row 为 3，用于追踪要添加内容的行数。
        i = 0
        for c in ['A', 'B', 'C', 'D', 'E', 'F']:
            if c == 'A':
                sheet.column_dimensions[c].width = 10.0
            elif c == 'C' or c == 'E' or c == 'F':
                sheet.column_dimensions[c].width = 20.0
            else:
                sheet.column_dimensions[c].width = 30.0
            sheet[c + '2'].value = title[i]
            sheet[c + '2'].font = Font(size=18, bold=True, color=Color(indexed=1))
            sheet[c + '2'].fill = PatternFill(patternType='solid', fgColor=Color(indexed=32))
            i += 1
        sheet.row_dimensions[1].height = 40
        sheet.row_dimensions[2].height = 30
        row = 3
        count = 1
        for v in ts.values():
            try:
                sheet.row_dimensions[row].height = 30
                sheet['A' + str(row)].value = count
                sheet['A' + str(row)].font = Font(size=16, bold=True)
                sheet['B' + str(row)].value = v.mac
                sheet['B' + str(row)].font = Font(size=16, bold=True)
                sheet['C' + str(row)].value = v.count
                sheet['C' + str(row)].font = Font(size=16, bold=True)
                sheet['D' + str(row)].value = v.cmd
                sheet['D' + str(row)].font = Font(size=16, bold=True)
                sheet['E' + str(row)].value = v.time
                sheet['E' + str(row)].font = Font(size=16, bold=True)
                sheet['F' + str(row)].value = "%.2f%%" % (v.per * 100)
                sheet['F' + str(row)].font = Font(size=16, bold=True)
            except Exception as e:
                print(e)
            row += 1
            count += 1
    sheet_delete = book.get_sheet_by_name('Sheet')
    book.remove_sheet(sheet_delete)
    book.save('report/' + get_time() + '_' + filename)

if __name__ == '__main__':
    for i in range(1, 10):
        print(i)

# 如果值的第一个字符为"D"，则将调用SmartPadding类生成填充数据，并将生成的数据加入到对应键的值列表中
# 如果值的第一个字符不为"D"，则直接将该值添加到对应键的值列表中
def build_sample(pre_sample: Dict[int, List[str]]) -> Dict[str, List[str]]:
    final_sample: Dict[str, List[str]] = {}
    for k in pre_sample.keys():
        key = pre_sample[k][0]
        final_sample[key] = []
        for v in range(1, len(pre_sample[k])):
            val = pre_sample[k][v]
            if val[0] == "D":  # dynamic
                final_sample[key] = final_sample[key] + SmartPadding(val).to_list()
            else:
                final_sample[key].append(val)
    return final_sample


# 根据mode属性的值（'R'或'A'），生成不同模式的数据：如果是'R'，则生成一个位于start和end之间的随机数；如果是'A'，则生成一个递增序列
class SmartPadding:
    def to_list(self) -> List[str]:
        """
            MODE: R: 随机 A: 顺序
            TYPE：H: HEX
        :return:
        """
        arr = []
        for i in range(0, self.time):
            v = 0
            if self.mode == 'R':
                v = random.randint(self.start, self.end)
            elif self.mode == 'A':
                v = self.start + i
            v = ("{:0" + str(self.len * 2) + "X}").format(v)
            v = " ".join(re.findall(r'.{2}', v))
            arr.append(v)
        return arr

    def __init__(self, script: str):
        """
        :param script: D(R,H,6,0x00,0xFF):10
               script: D(A,H,6,0x00):10

        """
        self.script = script
        d = self.script[1:].split(":")
        d[0] = d[0][1:-1]
        c = d[0].split(',')
        self.mode = c[0]
        self.type = c[1]
        self.len = int(c[2]) if len(c[2]) <= 2 else int(c[2], 16) if c[2][:2] == "0x" else int(c[2])
        self.start = int(c[3]) if len(c[3]) <= 2 else int(c[3], 16) if c[3][:2] == "0x" else int(c[3])
        if len(c) == 5:
            self.end = int(c[4]) if len(c[4]) <= 2 else int(c[4], 16) if c[4][:2] == "0x" else int(c[4])
        self.time = int(d[1]) if len(d[1]) <= 2 else int(d[1], 16) if d[1][:2] == "0x" else int(d[1])
        print(str(self))
